package gov.nasa.anml.lifted;

import java.util.ArrayList;

import gov.nasa.anml.PDDL;
import gov.nasa.anml.State;
import gov.nasa.anml.PDDL.Function;
import gov.nasa.anml.PDDL.FunctionReference;
import gov.nasa.anml.utility.*;
import static gov.nasa.anml.lifted.IdentifierCode.*;

// more or less a copy of fluent, but in a separate class and id-space
// because we can more or less compile these away during grounding
// the exception is action-local constants with initialization expressions.  
// These could change every time the action is invoked.

public class Constant<T extends SimpleObject<? super T>> extends IdentifierImp<T,SimpleVoid> implements ConstantExpression<T> {
	public Type<T> type; 
	//if type matches exactly (or subsumed by), don't have to check for
	// value inclusion into type, i.e., for enumerations
	// or structured types
	public T value;
	
	public Expression<T,?> init;
	// constant can mean with respect to the execution of a particular action
	// so that its value changes each time action fires, but, other than that is constant.
	
	
	
	public Constant() {
	}
	
	public Constant(String n) {
		name = new SimpleString(n);
	}
	
	public Constant(SimpleString n) {
		super(n);
	}

	public Constant(SimpleString n, Type<T> t) {
		super(n);
		this.type = t;
	}
	
	public Constant(SimpleString n, Type<T> t, Expression<T,?> init) {
		super(n);
		this.type = t;
		this.init = init;
	}
	
	public Constant(T v, SimpleString n, Type<T> t) {
		super(n);
		this.type = t;
		this.value = v;
		this.init = null;
	}
	
	
	public T value(State s) {
		return value;
	}
	
	// constants can change per action invocation
	// so this has to be re-evaluated in those states.
	// not that this approach will work, because there is only one
	// constant per action schema -- and even separate invocations
	// of the same schema anyways need multiple storage locations
	// (alternatively, one can view Start as a parameter)
	public T init(State s) {
		return value = init.value(s);
		//return (T) init.value(s);
		//return (T) s.get(id);
	}

	public TypeCode typeCode() {
		return type.typeCode();
	}
	
	public History<SimpleVoid> storage(State p, State c) {
		return null;
		// other possibilities:
		// return (StateValue<T>) s.get(id);
		// return (StateValue<T>) constantPool.get(id);
	}
	
	public IdentifierCode idCode() {
		return Constant;
	}
	
	public boolean apply(State p, int contextID, State c) {
		if (type.typeCode() != TypeCode.Boolean)
			return false;
		if (value(p) != ANMLBoolean.True)
			return false;
		return true;
	}

	public transient PDDL.Predicate boolPDDL;
	public transient PDDL.Function floatPDDL;
	public transient PDDL.PredicateReference asPDDLPredicateReference;
	public transient PDDL.FunctionReference asPDDLFunctionReference;
	public transient PDDL.Expression asPDDLExpression;
	
	public PDDL.Expression translateLValue(PDDL pddl,Interval unit) {
		if (asPDDLExpression != null)
			return asPDDLExpression;
		return super.translateLValue(pddl,unit);
	}

	public PDDL.Argument translateArgument(PDDL pddl, Interval unit) {
		System.err.println("Constants as arguments ought to work.  But it doesn't.");
		return super.translateArgument(pddl,unit);
	}
	
	public void translateDecl(PDDL pddl,Interval unit) {
		ArrayList<PDDL.Effect> effects = unit.getPDDLEffects();
		
		if (name == IntervalImp.durationName) {
			asPDDLExpression = pddl.DurationRef;
			return;
		}
		if (name == IntervalImp.startName) {
			System.err.println("PDDL lacks access to absolute time.  Substituting 0 for start.");
			asPDDLExpression = pddl.Zero;
			return;
		}
		if (name == IntervalImp.endName) {
			System.err.println("PDDL lacks access to absolute time.  Substituting ?duration for end.");
			asPDDLExpression = pddl.DurationRef;
			return;
		}

		if (init == null && value == null)
			System.err.println("Uh-oh, a constant lacking initialization.  Troublesome. " + this);
			
		int length = pddl.bufAppend(name);
		String nameString = pddl.bufToString();
		pddl.bufReset(length);
		
		switch(typeCode()) {
		case Boolean:
			if (boolPDDL != null)
				return;
			boolPDDL = pddl.new Predicate(nameString);
			boolPDDL.context.addAll(pddl.context);
			pddl.predicates.add(boolPDDL);
			asPDDLExpression = asPDDLPredicateReference = pddl.new PredicateReference(boolPDDL);
			if (init != null)
				init.translateStmt(pddl,unit,PDDL.Time.Start);
			else if (value != null) {
				effects.add(pddl.makeEffect(PDDL.Time.Start,asPDDLPredicateReference,((SimpleBoolean)value).v));
			}
			break;
		case Float:
			if (floatPDDL != null)
				return;
			floatPDDL = pddl.new Function(nameString);
			floatPDDL.context.addAll(pddl.context);
			pddl.functions.add(floatPDDL);
			asPDDLExpression = asPDDLFunctionReference = pddl.new FunctionReference(floatPDDL);
			if (init != null)
				init.translateStmt(pddl,unit,PDDL.Time.Start);
			else if (value != null) {
				effects.add(pddl.makeEffect(PDDL.Time.Start,PDDL.Op.assign,asPDDLFunctionReference,pddl.new FloatLiteral(((SimpleFloat)value).v)));				
			}
			break;
		case Integer:
			// be nice by relaxing integers to floats in the compilation to PDDL
			if (floatPDDL != null) // shouldn't happen...
				return;
			floatPDDL = pddl.new Function(nameString);
			floatPDDL.context.addAll(pddl.context);
			pddl.functions.add(floatPDDL);
			asPDDLExpression = asPDDLFunctionReference = pddl.new FunctionReference(floatPDDL);
			type = (Type<T>) Unit.floatType; 
			// make sure all future references see this as a float
			if (init != null)
				init.translateStmt(pddl,unit,PDDL.Time.Start);
			else if (value != null) {
				effects.add(pddl.makeEffect(PDDL.Time.Start,PDDL.Op.assign,asPDDLFunctionReference,pddl.new FloatLiteral(((SimpleInteger)value).v)));	
			}
			break;
		default:
			System.err.println("Unsupported type: " + this.type + " in: " + this + ".");
		}
	}

	public PDDL.Expression translateExpr(PDDL pddl, Interval unit) {
		if (asPDDLExpression != null)
			return asPDDLExpression;
		System.err.println("Oops: translateExpr() on a constant, " + this + ", without a PDDL.Expression attached.  Missing declaration?");
		return super.translateExpr(pddl,unit);
	}
	
	// interesting to test whether it figures out the method dispatch of super.translateStmt() `correctly'.
	public void translateStmt(PDDL pddl, Interval unit, PDDL.Time time) {
		translateStmt(this,pddl,unit,time);
	}

}
